package model;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.Set;

public class SPRoad implements ISPRoad {

	public class Cel {
		
		private IVertex departureVertex;
		private String edgeLabel;
		
		public Cel() {
			this.departureVertex = null;
			this.edgeLabel = null;
		}
		
		public Cel(IVertex departureVertex, String edgeLabel) {
			this.departureVertex = departureVertex;
			this.edgeLabel = edgeLabel;
		}

		public IVertex getDepartureVertex() {
			return departureVertex;
		}

		public void setDepartureVertex(IVertex departureVertex) {
			this.departureVertex = departureVertex;
		}

		public String getEdgeLabel() {
			return edgeLabel;
		}

		public void setEdgeLabel(String edgeLabel) {
			this.edgeLabel = edgeLabel;
		}
		
		
	}
	
	// FIELDS
	private LinkedList<Cel> trace;
	private Set<IVertex> circuitVertexes;
	private int currentIndex;
	
	// CONSTRUCTERS
	public SPRoad() {
		this.trace = new LinkedList<Cel>();
		this.circuitVertexes = new HashSet<IVertex>();
		this.currentIndex = -1;
	}
	
	// METHODS
	
	public Set<IVertex> getVertexes() {
		Set<IVertex> vertexes = new HashSet<IVertex>();
		for (Cel c: this.trace) {
			vertexes.add(c.getDepartureVertex());
		}
		return vertexes;
	}
	
	public Set<IVertex> getDepartureVertexes() {
		LinkedList<Cel> clone = (LinkedList<Cel>) this.trace.clone();
		clone.removeLast();
		Set<IVertex> vertexes = new HashSet<IVertex>();
		for (Cel c: clone) {
			vertexes.add(c.departureVertex);
		}
		return vertexes;
	}
	
	@Override
	public boolean addRoadEdge(IVertex departureVertex, String label,
			IVertex arrivalVertex) {
		if (departureVertex == null) {
			throw new IllegalArgumentException("departure vertex argument is null");
		}
		if (arrivalVertex == null) {
			throw new IllegalArgumentException("arrival vertex argument is null");
		}
		if (label == null) {
			throw new IllegalArgumentException("label argument is null");
		}
		
		Cel c;
		try {
			c = this.trace.getLast();
		} catch (NoSuchElementException e) {
			c = null;
		}
		
		if (c != null && c.getEdgeLabel() != null) {
			throw new IllegalStateException("Last edge must be null");
		}
		
		if (c == null) {
			// the road is empty
			this.trace.add(new Cel(departureVertex, label));
			this.trace.add(new Cel(arrivalVertex, null));
			return true;
		} else {
			IVertex v = c.getDepartureVertex();
			
			if (v.equals(departureVertex)) { 
				Set<IVertex> s = getDepartureVertexes();
				
				if (s.contains(v)) {
					// v is already a departure vertex
					if (this.circuitVertexes.contains(v)) {
						// v is already a circuit vertex
						c.setEdgeLabel(label);
						this.trace.add(new Cel(arrivalVertex, null));
						
						if (getDepartureVertexes().contains(arrivalVertex)) {
							// a circuit is detected at the arrival vertex
							this.circuitVertexes.add(arrivalVertex);
						}
						return true;
					} 
				} else {
					// v is not a departure vertex
					c.setEdgeLabel(label);
					this.trace.add(new Cel(arrivalVertex, null));
				
					if (getDepartureVertexes().contains(arrivalVertex)) {
						// a circuit is detected at the arrival vertex
						this.circuitVertexes.add(arrivalVertex);
					}
					return true;
				}
			}
		}
		return false;
	}

	@Override
	public void backToBeginning() {
		this.currentIndex = 0;
	}

	@Override
	public IVertex getArrivalVertex() {
		IVertex v;
		Cel c;
		try {
			c = this.trace.getLast();
			v = c.getDepartureVertex();
		} catch (NoSuchElementException e) {
			v = null;
		}
		return v;
	}

	@Override
	public IVertex getDepartureVertex() {
		IVertex v;
		Cel c;
		try {
			c = this.trace.getFirst();
			v = c.getDepartureVertex();
		} catch (NoSuchElementException e) {
			v = null;
		}
		return v;
	}

	@Override
	public IVertex next() {
		IVertex v;
		this.currentIndex++;
		try {
			Cel c = this.trace.get(this.currentIndex); 
			v = c.getDepartureVertex();
		} catch (IndexOutOfBoundsException e) {
			v = null;
			this.currentIndex--;
		}
		return v;
	}
	
	public IVertex getNextVertex() {
		IVertex v;
		try {
			Cel c = this.trace.get(this.currentIndex + 1); 
			v = c.getDepartureVertex();
		} catch (NoSuchElementException e) {
			v = null;
		}
		return v;
	}
	
	@Override
	public String getNextLabel() {
		String label;
		try {
			Cel c = this.trace.get(this.currentIndex);
			label = c.getEdgeLabel();
		} catch (NoSuchElementException e) {
			label = null;
		}
		return label;
	}
	
	@Override
	public int getLength() {
		return this.trace.size();
	}
	
	@Override
	public boolean isEmpty() {
		return this.trace.isEmpty();
	}
	
	@Override
	public ISPRoad getSubRoad(int startIndex, int endIndex) {
		if (startIndex < 0) {
			throw new IllegalArgumentException(startIndex + " :start index < 0");
		}
		if (endIndex >= this.trace.size()) {
			throw new IllegalArgumentException(endIndex + " :start index > list length");
		}
		if (startIndex > endIndex) {
			throw new IllegalArgumentException("start index > end index");
		}
		ISPRoad road = new SPRoad();
		IVertex departure;
		String label;
		IVertex arrival;
		int i = startIndex;
		int tempIndex = this.currentIndex; 
		this.currentIndex = startIndex - 1;
		while (i < endIndex) {
			departure = this.next();
			label = this.getNextLabel();
			arrival = this.getNextVertex();
			road.addRoadEdge(departure, label, arrival);
			i++;
		}
		this.currentIndex = tempIndex;
		return road;
	}
	
	@Override
	public String getLastEdgeLabel() {
		Cel c = this.trace.get(this.trace.size() - 2);
		return c.getEdgeLabel();
	}
	
	public LinkedList<Cel> getTrace() {
		return trace;
	}

	public Set<IVertex> getCircuitVertexes() {
		return circuitVertexes;
	}
	
	public int getCurrentIndex() {
		return currentIndex;
	}

	public String toString() {
		String res = "Road: ";
		for (Cel c: this.trace) {
			res += "(" + c.getDepartureVertex().getContent() + ")";
			if (c.getEdgeLabel() != null) {
				res += "--|" + c.getEdgeLabel() + "|-->";
			}
		}
		return res;
	}
	
	@Override
	public boolean removeLastEdge() {
		Cel c;
		if (!this.trace.isEmpty()) {
			this.trace.removeLast();
			if (!this.trace.isEmpty()) {
				c = this.trace.getLast();
				c.setEdgeLabel(null);
			}
			return true;
		}
		return false;
	}
	
	public void setTrace(LinkedList<Cel> trace) {
		this.trace.clear();
		for (Cel c : trace) {
			this.trace.add(c);
		}
	}
	
	public void setCircuitVertexes(Set<IVertex> c) {
		circuitVertexes.addAll(c);
	}
	
	public void setCurrentIndex(int i) {
		currentIndex = i;
	}
	
	public SPRoad copy(SPRoad r) {
		SPRoad road = new SPRoad();
		
		LinkedList<Cel> trace = new LinkedList<Cel>();
		for (Cel c: r.getTrace()) {
			trace.add(new Cel(c.getDepartureVertex(), c.getEdgeLabel()));
		}
		road.setTrace(trace);
		road.setCircuitVertexes(r.getCircuitVertexes());
		road.setCurrentIndex(r.getCurrentIndex());
		return road;
	}

}
